package com.stuffthathappens.concurrency.donut;

import com.stuffthathappens.concurrency.Donut;
import com.stuffthathappens.concurrency.SampleThreadFactory;

import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Random;
import java.awt.event.ActionEvent;
import java.awt.*;

/**
 * @author Eric M. Burke
 */
public class DonutProducerPanel extends JPanel {
    private final BlockingQueue<Donut> queue;
    private final Random rand = new Random();

    private final AtomicInteger numProduced = new AtomicInteger(0);
    private final JLabel numProducedLabel = new JLabel("0");
    private final String producerName;
    private final AtomicInteger delay = new AtomicInteger(500);
    private final JSlider speedSlider = new JSlider(1, 1000, delay.get());

    private ExecutorService executorService;

    private final Action startAction = new AbstractAction("Start") {
        public void actionPerformed(ActionEvent e) {
            startClicked();
        }
    };
    private final Action stopAction = new AbstractAction("Stop") {
        public void actionPerformed(ActionEvent e) {
            stopClicked();
        }
    };

    public DonutProducerPanel(String producerName,
                              BlockingQueue<Donut> queue) {
        super(new BorderLayout());
        this.producerName = producerName;
        this.queue = queue;
        setBorder(BorderFactory.createTitledBorder(producerName));

        add(createSpeedPanel(), BorderLayout.NORTH);
        add(createButtonPanel(),BorderLayout.SOUTH);
        updateEnabledStates();

        // this listener is invoked on the EDT
        speedSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                // store the value in AtomicInteger, which is of course thread safe
                delay.set(speedSlider.getValue());
            }
        });
    }

    private Component createSpeedPanel() {
        JPanel p = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.insets = new Insets(4,4,4,4);
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.anchor = GridBagConstraints.WEST;
        p.add(new JLabel("Speed:"), gbc);

        gbc.gridy++;
        p.add(new JLabel("Num Produced:"), gbc);

        gbc.gridx = 1;
        gbc.gridy = 0;
        gbc.weightx = 1.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        p.add(speedSlider, gbc);

        gbc.gridy++;
        p.add(numProducedLabel, gbc);

        return p;
    }

    private void startClicked() {
        numProduced.set(0);
        numProducedLabel.setText("0");

        executorService = Executors.newSingleThreadExecutor(
                new SampleThreadFactory("donutProducer"));
        executorService.execute(new ProducerRunnable());
        updateEnabledStates();
    }

    private Component createButtonPanel() {
        Box box = Box.createHorizontalBox();
        box.add(Box.createHorizontalStrut(4));
        box.add(Box.createHorizontalGlue());
        box.add(new JButton(startAction));
        box.add(Box.createHorizontalStrut(4));
        box.add(new JButton(stopAction));
        box.add(Box.createHorizontalStrut(4));
        return box;
    }

    private void stopClicked() {
        executorService.shutdownNow();
        executorService = null;
        updateEnabledStates();
    }

    private void updateEnabledStates() {
        startAction.setEnabled(executorService == null);
        stopAction.setEnabled(executorService != null);
    }

    // generates a random donut
    private Donut makeDonut() {
        final Donut.Type[] allTypes = Donut.Type.values();
        return new Donut(producerName, allTypes[rand.nextInt(allTypes.length)]);
    }

    private class ProducerRunnable implements Runnable {
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    TimeUnit.MILLISECONDS.sleep(1000 - delay.get());
                    Donut d = makeDonut();

                    // put will block until space is available
                    queue.put(d);

                    numProduced.incrementAndGet();

                    // update the total count label
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            numProducedLabel.setText(Integer.toString(numProduced.get()));
                        }
                    });
                } catch (InterruptedException e) {
                    return;
                }
            }
        }
    }
}
